package com.atguigu.study.aspect;

import com.atguigu.study.annotations.MyRedisCache;
import jakarta.annotation.Resource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/**
 * 定义一个切面类
 */
@Component
@Aspect
public class MyRedisCacheAspect {
    @Resource
    private RedisTemplate redisTemplate;

    //配置织入点
    @Pointcut("@annotation(com.atguigu.study.annotations.MyRedisCache)")
    public void cachePointCut() {
    }

    @Around("cachePointCut()")
    public Object doCache(ProceedingJoinPoint joinPoint) {
        Object result = null;


        try {
            //1.获得重载后的方法名,底层通过反射
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();

            //2.确定方法名后获得该方法上面配置的注解标签MyRedisCache
            MyRedisCache methodAnnotation = method.getAnnotation(MyRedisCache.class);

            //3.拿到了MyRedisCache这个注解标签,获得该注解上面配置的参数进行封装和调用
            String keyPrefix = methodAnnotation.keyPrefix();
            String matchValueSptingEL = methodAnnotation.matchValue();

            //4.SpringEL解析器
            SpelExpressionParser parser = new SpelExpressionParser();
            //转换#id
            Expression expression = parser.parseExpression(matchValueSptingEL);
            //创建一个对象
            StandardEvaluationContext context = new StandardEvaluationContext();

            //5.使用反射获得方法里面的参数
            //传过来的ID参数230
            Object[] args = joinPoint.getArgs();
            //参数类型和id
            Parameter[] parameterNames = method.getParameters();
            for (int i = 0; i < parameterNames.length; i++) {
                System.out.println("获得方法里参数名和值:" + parameterNames[i].getName() + "\t" + args[i].toString());
                //#id被230替换
                context.setVariable(parameterNames[i].getName(), args[i].toString());


            }
            //6.通过上述,拼接redis的最终形式
            //user:230
            String key = keyPrefix + ":" + expression.getValue(context).toString();
            System.out.println("----拼接redis的最终key形式:" + key);

            //7.判断redis里面是否有该key,有就直接返回,没有就调用方法,再把结果存入redis
            result = redisTemplate.opsForValue().get(key);
            if (result != null) {
                System.out.println("-------redis里面有,直接返回,不去打扰mySql = " + result);
                return result;
            }
            //8 redis里面没有，去找msyql查询或叫进行后续业务逻辑
            //-------aop部分,才去找getUserById方法干活
            //主业务逻辑查询mysql,放行放行放行userMapper.selectById(id);
            result = joinPoint.proceed();

            //9 mysql步骤结束，还需要把结果存入redis一次，缓存补偿
            if (result != null) {
                System.out.println("------redis里面无，还需要把结果存入redis一次，缓存补偿: " + result);
                redisTemplate.opsForValue().set(key, result);
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return result;

    }

}
